/*
 ** selectserver.c -- servidor de chat multiusuario
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "../../Commons/src/includes/FuncionesLog.h"
#include "../../Commons/src/includes/BibliotecasGenerales.h"
#include "../../Commons/src/includes/nipc.h"
#include "./includes/variablesGlobales.h"
#include "./includes/FuncionesArchConfig.h"
#include "./includes/funcionesUtiles.h"
#include "./includes/funcionesAlgoritmo.h"
#include "./includes/colasPeticion.h"
#include "./includes/conexiones.h"



//#define PORT 9067 // puerto en el que escuchamos

int main(void)
{
	fd_set master; // conjunto maestro de descriptores de fichero
	fd_set read_fds; // conjunto temporal de descriptores de fichero para select();
	struct sockaddr_in myaddr;// dirección del servidor
	struct sockaddr_in remoteaddr; // dirección del cliente
	int fdmax; // número máximo de descriptores de fichero
	int listener; // descriptor de socket a la escucha
	int newfd; // descriptor de socket de nueva conexión aceptada
	char buf[528]; // buffer para datos del cliente
	int nbytes;
	int yes=1; // para setsockopt() SO_REUSEADDR, más abajo
	int addrlen;
	int i;
	char bufferLog[100];


	uint32_t cant_PDD = 0;
	paqueteNIPC paquete;
	paqueteNIPC *paqueteP;
	uint32_t numeroSector;
	payloadEscritura_t payloadEscritura;
	pthread_t hiloSincro;
	sincro_t parametros;
	flagConsola=1; //0 apagada, 1 prendida
	time_t curtime;
	struct tm *loctime;
	datosPDD_t datos_PDD;
	uint32_t cant_PFS;
	uint32_t n;
	uint32_t cantMaxPFS;
	contador = 0;
	semaforo = 0;
	nodo_t nodoAux;

//	paqueteP = malloc(sizeof(paqueteNIPC));

	cargarArchivoDeConfiguracion();

	if(flagConsola == 1){
		puts("El RAID entro en funcionamiento");
	};

	inicializarPFS(socketPFS,10);

	FD_ZERO(&master); // borra los conjuntos maestro y temporal
	FD_ZERO(&read_fds); // obtener socket a la escucha

	inicializarVectorPDDs(desc_PDD,10);

	if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		if(flagLog!=0){
		sprintf(bufferLog,"Error de conexion en el listener");
		agregarEntradaLog("Error",bufferLog);}
		exit(1);
	}

	// obviar el mensaje "address already in use" (la dirección ya se está usando)
	if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes,
			sizeof(int)) == -1) {
		perror("setsockopt");
		if(flagLog!=0){
			sprintf(bufferLog,"Error de conexion en el setsockopt");
			agregarEntradaLog("Error",bufferLog);}
		exit(1);
	}

	// enlazar
	myaddr.sin_family = AF_INET;
	myaddr.sin_addr.s_addr = INADDR_ANY;
	myaddr.sin_port = htons(PuertoDelRAID);
	memset(&(myaddr.sin_zero), '\0', 8);

	if (bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1) {
		perror("bind");
		if(flagLog!=0){
			sprintf(bufferLog,"Error de binding");
			agregarEntradaLog("Error",bufferLog);}
		exit(1);
	}

	// escuchar
	if (listen(listener, 10) == -1) {
		perror("listen");
		if(flagLog!=0){
			sprintf(bufferLog,"Error de conexion en el listen");
			agregarEntradaLog("Error",bufferLog);}
		exit(1);
	}

	if(flagLog!=0){
		sprintf(bufferLog,"Error de conexion en el listener");
		agregarEntradaLog("Error",bufferLog);}

	// añadir listener al conjunto maestro
	FD_SET(listener, &master);

	// seguir la pista del descriptor de fichero mayor
	fdmax = listener; // por ahora es éste

	// bucle principal
	for(;;) {
		paqueteP = malloc(sizeof(paqueteNIPC));
		read_fds = master; // cópialo
		if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
			perror("select");
			if(flagLog!=0){
				sprintf(bufferLog,"Error de conexion en el select");
				agregarEntradaLog("Error",bufferLog);}
			exit(1);
		}

		// explorar conexiones existentes en busca de datos que leer
		for(i = 0; i <= fdmax; i++) {
			if (FD_ISSET(i, &read_fds)) { // ¡¡tenemos datos!!

				if (i == listener) {
					// gestionar nuevas conexiones
					addrlen = sizeof(remoteaddr);
					if ((newfd = accept(listener, (struct sockaddr*)&remoteaddr,&addrlen)) == -1)
					{
						perror("accept");
						if(flagLog!=0){
							sprintf(bufferLog,"Error de conexion en el accept");
							agregarEntradaLog("Error",bufferLog);}
					} else {
//						if (fcntl(newfd, F_SETFL, flags) <0){
//
//							perror("Error de F_SETFL:");
//
//						};
//						puts("Llega aquí");

						//Handshake
						if(recv(newfd, &paquete, sizeof(paquete), MSG_WAITALL)==-1){
							perror("recv");
							if(flagLog!=0){
								sprintf(bufferLog,"Error de conexion en el receive");
								agregarEntradaLog("Error",bufferLog);}
						}else{
//							puts("Llega acuá");

							if(paquete.num_msg==0){
								if(paquete.longitud == 0){ //es PFS
									memcpy(&cantMaxPFS,paquete.payload,sizeof(uint32_t));
									if(cant_PFS<cantMaxPFS){
									for(n=0;n<cantMaxPFS;n++){
									if(socketPFS[n]==-1){
									socketPFS[n]=newfd;
									n=cantMaxPFS;
									cant_PFS++;
									}
									}



									if(cant_PDD == 0){
										paqueteP->num_msg=-1;
										strcpy(paqueteP->payload,"El RAID no tiene ningún PDD");
										paqueteP->longitud=sizeof("El RAID no tiene ningún PDD");
										if(send(newfd,paqueteP,sizeof(paqueteNIPC),0)==-1){
											perror("send");};
										if(flagLog!=0){
											sprintf(bufferLog,"Error de conexion en el send");
											agregarEntradaLog("Error",bufferLog);}
									} else {

//										printf("El socket del PFS es %d\n",newfd);
										paqueteP->num_msg = 0;
										paqueteP->longitud = 0;

										if(send(newfd,paqueteP,sizeof(paqueteNIPC),0)==-1){
											perror("send");
											if(flagLog!=0){
															sprintf(bufferLog,"Error de conexion en el send");
															agregarEntradaLog("Error",bufferLog);}
											puts("Error de respuesta al PFS en el HS\n");
										} else {
											if(flagLog!=0){
															sprintf(bufferLog,"Se conecto un PFS con exito");
															agregarEntradaLog("HandShake PFS",bufferLog);}
										//	puts("HS con PFS exitoso\n");
										};
									};
									} else {
									paqueteP->num_msg=-2;
									strcpy(paqueteP->payload,"Se llegó al máximo de conexiones de PFS");
									paqueteP->longitud=sizeof("Se llegó al máximo de conexiones de PFS");
									if(send(newfd,paqueteP,sizeof(paqueteNIPC),0)==-1){
										perror("send");};
/*									printf("Se llegó al máximo de conexiones de PFS");*/};
								} else {
									//puts("Llega acá");

//									desc_PDD[cant_PDD].colaFte = malloc(sizeof(nodo_t));
//									desc_PDD[cant_PDD].colaFin = malloc(sizeof(nodo_t));

									desc_PDD[cant_PDD].socket = newfd; //agrego socket del PDD
									desc_PDD[cant_PDD].colaFte = NULL;
									desc_PDD[cant_PDD].colaFin = NULL;
									desc_PDD[cant_PDD].flagSincro = 0;

									memcpy(&datos_PDD,paquete.payload,sizeof(paquete.payload));
									strcpy(desc_PDD[cant_PDD].identificador,datos_PDD.nome);
									if(flagConsola==1){
														puts("Se conecto el PDD: ");
														printf("%s \n",desc_PDD[cant_PDD].identificador);
														};

									pthread_create(&desc_PDD[cant_PDD].hilo,NULL,(void*)&tomarPeticion,(void*) &desc_PDD[cant_PDD]);

//									puts("Data\n");
//									printf("socket %d\n",desc_PDD[cant_PDD].socket);
//									printf("flag sincro %d\n",desc_PDD[cant_PDD].flagSincro);
//									printf("cant PDDs %d\n",cant_PDD);
//									printf("nombre %s\n",desc_PDD[cant_PDD].identificador);

									paqueteP->longitud = 0;
									paqueteP->num_msg = 0;


									if(send(newfd,paqueteP,sizeof(paqueteNIPC),0)==-1){
										perror("send");
										if(flagLog!=0){
														sprintf(bufferLog,"Error de conexion en el send");
														agregarEntradaLog("Error",bufferLog);}
										puts("Error de respuesta al PDD en el HS\n");
									} else {
										//puts("HS con PDD exitoso\n");
										if(flagLog!=0){
														sprintf(bufferLog,"Se conecto un nuevo PDD");
														agregarEntradaLog("Handshake PDD",bufferLog);}									};


									if(cant_PDD == 0){
//										colaAuxFte = malloc(sizeof(nodo_t));
//										colaAuxFin = malloc(sizeof(nodo_t));
//
//										colaAuxFte = NULL;
//										colaAuxFin = NULL;

										desc_PDD[cant_PDD].flagSincro = 1;
										sectoresPorDisco = datos_PDD.sectoresPorDisco;
									} else {

										//sectoresPorDisco = 10;//datos_PDD.sectoresPorDisco;
//para probar
										parametros.cantPDD = cant_PDD;
										parametros.colaFte = NULL;
										parametros.colaFin = NULL;
										parametros.sectoresPorDisco = sectoresPorDisco;
										memcpy(&parametros.desc_PDD, &desc_PDD, sizeof(desc_PDD_t));

										pthread_create(&hiloSincro,NULL,(void*)&sincronizar,(void*) &parametros);

										if(flagConsola==1){
											curtime = time (NULL);											       loctime = localtime (&curtime);
											printf("El disco se empezó a sincronizar");
											fputs (asctime (loctime), stdout);
										};
								}

									cant_PDD++;
								};



								FD_SET(newfd, &master); // añadir al conjunto maestro

								if (newfd > fdmax) {
									// actualizar el máximo
									fdmax = newfd;
								}
//								printf("selectserver: new connection from %s on "
	//									"socket %d\n", inet_ntoa(remoteaddr.sin_addr),
		//								newfd);

			/*				} else {puts("No es handshake");*/}
						}
					}
				} else {
					// gestionar datos de un cliente
					if ((nbytes = recv(i, buf, sizeof(buf), MSG_WAITALL)) <= 0) {
						// error o conexión cerrada por el cliente
						if (nbytes == 0) {
							// conexión cerrada
				//			printf("selectserver: socket %d hung up\n", i);
							if(flagLog!=0){
											sprintf(bufferLog,"Se Desconecto un Proceso");
											agregarEntradaLog("Desconexion",bufferLog);}
							if(esPFS(i,socketPFS,cantMaxPFS)){
								for(n=0;n<cantMaxPFS;n++)
								{
								if(socketPFS[n]==i){
								socketPFS[n]=-1;
								};
								};
								cant_PFS--;
							} else {
								if(esPDD(i,desc_PDD,cant_PDD)){
								quitarElementoDeVector(i,desc_PDD,cant_PDD);
								cant_PDD--;
//								if(cant_PDD==0){
//									free(colaAuxFte);
//									free(colaAuxFin);
//								}
								};
							};
						} else {
							perror("recv");
							if(flagLog!=0){
							sprintf(bufferLog,"Error en el receive");
							agregarEntradaLog("Error",bufferLog);}
						}

						close(i); // ¡Hasta luego!


						FD_CLR(i, &master); // eliminar del conjunto maestro

					} else {

						/*Nos habla un PFS o PDD*/
						if(esPFS(i,socketPFS,cantMaxPFS) || esPDD(i,desc_PDD,cant_PDD)){
							memcpy(&paquete, buf, sizeof(paqueteNIPC));
							switch(paquete.num_msg){
							case 1:
								// Pedido de lectura (PFS)
								if(flagLog!=0){
												sprintf(bufferLog,"Se solicita una Lectura");
												agregarEntradaLog("Pedido",bufferLog);}
								memcpy(&numeroSector, paquete.payload, sizeof(uint32_t));
								semaforo--;
								otorgarPeticionDisco(numeroSector, cant_PDD, sectoresPorDisco, desc_PDD, i); // Harcodeamos los sectores por disco
								break;
							case 2:
								// Devuelve lectura (PDD)


								paqueteP->num_msg = 2;
								paqueteP->longitud = sizeof(paquete.payload);

								memcpy(&payloadEscritura,paquete.payload,sizeof(payloadEscritura_t));

								nodoAux.peticion = payloadEscritura.sector; //NEW FRIENDS
							//	suprimirNodoAux(colaAuxFte,nodoAux);

								memcpy(&paqueteP->payload, paquete.payload, 520); //COPIA TODO SALVO LOS ULTIMOS 4 BYTES QUE SON DEL SOCKET

									if(send(payloadEscritura.socket, paqueteP, sizeof(paquete),0) == -1){
										perror("send");
										if(flagLog!=0){
														sprintf(bufferLog,"Error en el send");
														agregarEntradaLog("Error",bufferLog);}
										puts("error al devolver contenido pedido al PFS");
									};
								semaforo++;

								break;
							case 3:
							//Peticion de escritura (PFS)
								if(flagLog!=0){
												sprintf(bufferLog,"Peticion de Escritura");
												agregarEntradaLog("Peticion",bufferLog);};
//								paqueteP->num_msg = 3;
//								paqueteP->longitud = sizeof(paquete.payload);
								memcpy(&payloadEscritura, paquete.payload, sizeof(payloadEscritura));
								payloadEscritura.socket = i;
								semaforo = semaforo - cant_PDD;
								encolarParaEscritura(desc_PDD, cant_PDD, payloadEscritura, sizeof(payloadEscritura));
								break;
							case 4:
								//Devuelve ok de escritura (PDD)
								paqueteP->num_msg = 4;
								paqueteP->longitud = sizeof(paquete.payload);
								memcpy(&payloadEscritura,paquete.payload,sizeof(payloadEscritura));

								memcpy(&paqueteP->payload, paquete.payload, sizeof(paquete.payload));

//									if(send(payloadEscritura.socket, paqueteP, sizeof(paquete),0) == -1){
//										perror("send");
//										puts("error al devolver contenido pedido al PFS");
//									};
								semaforo++;
								break;
							case 5:
								//Devuelve lectura para Sincro (PDD)
								if(flagLog!=0){
												sprintf(bufferLog,"Pedido de Escritura para Sincronizacion");
												agregarEntradaLog("Pedido",bufferLog);}
								memcpy(&payloadEscritura, paquete.payload, sizeof(payloadEscritura));
					//			puts("Se encola para escritura Sincro");
								encolarParaEscrituraSincro(desc_PDD, cant_PDD, payloadEscritura, sizeof(payloadEscritura),parametros.cantPDD);
								break;
							default:
								break;
							}
						}



					}
				} // Esto es ¡TAN FEO!
			}
		}
	free(paqueteP);
	}

	return 0;
}

